﻿// 2023/3/8: 首个版本
// 2023/6/7: 修复安装插件后搜索崩溃问题
// 2023/9/22: 使用PassParent给ShowDialog传参
// 2023/9/25: 改为直接输入Icon对象
// 2024/6/25: 支持API3，支持异步
// 2024/11/27: 支持客户端获取主机端插件信息
// 2024/12/3: 使用App.ShowSelectFolderDialog
// 2025/1/10: 客户端模式下不显示原生层信息
// 2025/1/16: 显式指定面板模式

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ASEva;
using ASEva.Utility;
using ASEva.UIEto;
using Eto.Forms;
using Eto.Drawing;

namespace CommonSource
{
    class PluginLibraryDialog : DialogPanel
    {
        public PluginLibraryDialog(Icon icon = null, object caller = null)
        {
            var lang = AgencyLocal.GetAppLanguage();
            var chinese = lang == Language.Chinese;
            var t = TextResource.Load(Convert.FromBase64String("PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHRleHRfcmVzb3VyY2U+CiAgPHQgaWQ9ImFwcGxpY2F0aW9uLWxheWVyIj4KICAgIDxlbj5BcHBsaWNhdGlvbiBsYXllcjo8L2VuPgogICAgPHpoPuW6lOeUqOWxgjo8L3poPgogIDwvdD4KICA8dCBpZD0iYnJpZWYiPgogICAgPGVuPkJyaWVmOjwvZW4+CiAgICA8emg+566A5LuLOjwvemg+CiAgPC90PgogIDx0IGlkPSJjb25maXJtLWRpc2FibGUiPgogICAgPGVuPkFyZSB5b3Ugc3VyZSB0byBkaXNhYmxlIGFsbCBwbHVnaW5zPzwvZW4+CiAgICA8emg+56Gu6K6k5piv5ZCm56aB55So5omA5pyJ5o+S5Lu2Pzwvemg+CiAgPC90PgogIDx0IGlkPSJjb25maXJtLWVuYWJsZSI+CiAgICA8ZW4+QXJlIHlvdSBzdXJlIHRvIGVuYWJsZSBhbGwgcGx1Z2lucz88L2VuPgogICAgPHpoPuehruiupOaYr+WQpuWQr+eUqOaJgOacieaPkuS7tj88L3poPgogIDwvdD4KICA8dCBpZD0iY29uZmlybS11bmluc3RhbGwiPgogICAgPGVuPkFyZSB5b3Ugc3VyZSB0byB1bmluc3RhbGwgcGx1Z2luJ3swfSc/PC9lbj4KICAgIDx6aD7noa7orqTmmK/lkKbljbjovb3mj5Lku7YnezB9Jz88L3poPgogIDwvdD4KICA8dCBpZD0iZGlzYWJsZS1hbGwtcGx1Z2lucyI+CiAgICA8ZW4+RGlzYWJsZSBhbGwgcGx1Z2luczwvZW4+CiAgICA8emg+56aB55So5YWo6YOo5o+S5Lu2PC96aD4KICA8L3Q+CiAgPHQgaWQ9ImRpc2FibGVkIj4KICAgIDxlbj5EaXNhYmxlZDwvZW4+CiAgICA8emg+5bey56aB55SoPC96aD4KICA8L3Q+CiAgPHQgaWQ9ImVuYWJsZS1hbGwtcGx1Z2lucyI+CiAgICA8ZW4+RW5hYmxlIGFsbCBwbHVnaW5zPC9lbj4KICAgIDx6aD7lkK/nlKjlhajpg6jmj5Lku7Y8L3poPgogIDwvdD4KICA8dCBpZD0iZW5hYmxlZCI+CiAgICA8ZW4+RW5hYmxlZDwvZW4+CiAgICA8emg+5bey5ZCv55SoPC96aD4KICA8L3Q+CiAgPHQgaWQ9ImluZm9ybWF0aW9uIj4KICAgIDxlbj5JbmZvcm1hdGlvbjwvZW4+CiAgICA8emg+5o+S5Lu25L+h5oGvPC96aD4KICA8L3Q+CiAgPHQgaWQ9Imluc3RhbGwtcGx1Z2luIj4KICAgIDxlbj5JbnN0YWxsIHBsdWdpbjwvZW4+CiAgICA8emg+5a6J6KOFPC96aD4KICA8L3Q+CiAgPHQgaWQ9Imluc3RhbGwtdGl0bGUiPgogICAgPGVuPlBsZWFzZSBzZWxlY3QgdGhlIGZvbGRlciBjb250YWluaW5nIHBsdWdpbihzKS48L2VuPgogICAgPHpoPumAieaLqeWMheWQq+aPkuS7tueahOaWh+S7tuWkuTwvemg+CiAgPC90PgogIDx0IGlkPSJsb2FkLWZhaWxlZCI+CiAgICA8ZW4+TG9hZCBmYWlsZWQ8L2VuPgogICAgPHpoPuWKoOi9veWksei0pTwvemg+CiAgPC90PgogIDx0IGlkPSJuYXRpdmUtbGF5ZXIiPgogICAgPGVuPk5hdGl2ZSBsYXllcjo8L2VuPgogICAgPHpoPuWOn+eUn+Wxgjo8L3poPgogIDwvdD4KICA8dCBpZD0ibm9uZSI+CiAgICA8ZW4+KE5vbmUpPC9lbj4KICAgIDx6aD4o5peg5L+h5oGvKTwvemg+CiAgPC90PgogIDx0IGlkPSJndWktdW5zdXBwb3J0ZWQiPgogICAgPGVuPkdVSSB1bnN1cHBvcnRlZDwvZW4+CiAgICA8emg+R1VJ5LiN5pSv5oyBPC96aD4KICA8L3Q+CiAgPHQgaWQ9InJlc3RhcnQtbm90aWNlIj4KICAgIDxlbj5QbGVhc2UgcmVzdGFydCBzb2Z0d2FyZSB0byBhY3RpdmF0ZSB0aGUgZW5hYmxlZCBwbHVnaW5zLjwvZW4+CiAgICA8emg+6K+36YeN5ZCv6L2v5Lu25Lul5r+A5rS75ZCv55So55qE5o+S5Lu244CCPC96aD4KICA8L3Q+CiAgPHQgaWQ9InRpdGxlIj4KICAgIDxlbj5QbHVnaW4gTGlicmFyeTwvZW4+CiAgICA8emg+5o+S5Lu2566h55CGPC96aD4KICA8L3Q+CiAgPHQgaWQ9InRvLWJlLWVuYWJsZWQiPgogICAgPGVuPlRvIGJlIGVuYWJsZWQ8L2VuPgogICAgPHpoPuS4i+asoeWQr+eUqDwvemg+CiAgPC90PgogIDx0IGlkPSJ1bmluc3RhbGwiPgogICAgPGVuPlVuaW5zdGFsbDwvZW4+CiAgICA8emg+5Y246L29PC96aD4KICA8L3Q+CiAgPHQgaWQ9InVubGljZW5zZWQiPgogICAgPGVuPlVubGljZW5zZWQ8L2VuPgogICAgPHpoPuacquiuuOWPrzwvemg+CiAgPC90PgogIDx0IGlkPSJ2ZXJzaW9uIj4KICAgIDxlbj5WZXJzaW9uOjwvZW4+CiAgICA8emg+54mI5pysOjwvemg+CiAgPC90Pgo8L3RleHRfcmVzb3VyY2U+"), lang);

            Icon = icon;
            Title = t["title"];
            SetResizableMode(910, 610, 910, 610);

            if (AgencyLocal.ClientSide)
            {
                if (caller != null)
                {
                    var tabControl = this.SetContentAsColumnLayout(0).AddControl(new TabControl(), true);
                    panelLocal = tabControl.AddPage(chinese ? "本地" : "Local").SetContentAsControl(new PluginLibraryPanel(PluginLibraryPanelMode.ClientLocal, t), 0);
                    panelRemote = tabControl.AddPage(chinese ? "远程" : "Remote").SetContentAsControl(new PluginLibraryPanel(PluginLibraryPanelMode.HostRemote, t, caller), 0);
                }
                else
                {
                    panelLocal = this.SetContentAsControl(new PluginLibraryPanel(PluginLibraryPanelMode.ClientLocal, t), 0);
                }
            }
            else
            {
                panelLocal = this.SetContentAsControl(new PluginLibraryPanel(PluginLibraryPanelMode.HostLocal, t), 0);
            }
        }

        public bool LocalModified => panelLocal.Modified;
        public bool RemoteModified => panelRemote?.Modified ?? false;

        private PluginLibraryPanel panelLocal, panelRemote;
    }

    enum PluginLibraryPanelMode
    {
        HostLocal,
        HostRemote,
        ClientLocal,
    }

    class PluginLibraryPanel : Panel
    {
        public PluginLibraryPanel(PluginLibraryPanelMode mode, TextResource t, object caller = null)
        {
            this.t = t;

            var mainLayout = this.SetContentAsRowLayout(8, 8, VerticalAlignment.Stretch);
            var leftLayout = mainLayout.AddColumnLayout(false, 300, 0, 8);
            var rightLayout = mainLayout.AddColumnLayout(true);

            var searchBox = leftLayout.AddControl(new SearchBox());
            var listBox = leftLayout.AddControl(new ListBox(), true);
            var enableDisableLayout = leftLayout.AddRowLayout();
            var buttonEnableAll = enableDisableLayout.AddButton(t["enable-all-plugins"], true);
            var buttonDisableAll = enableDisableLayout.AddButton(t["disable-all-plugins"], true);
            var buttonInstall = leftLayout.AddButton(t["install-plugin"]);

            var topLayout = rightLayout.AddRowLayout();
            var groupLayout = rightLayout.AddGroupBox(t["information"], true).SetContentAsColumnLayout();

            var checkEnabled = topLayout.AddCheckBox("-");
            checkEnabled.SetLogicalWidth(200);
            checkEnabled.Enabled = false;
            topLayout.AddSpace();
            LinkButton buttonUninstall = null;
            if (mode == PluginLibraryPanelMode.HostLocal || mode == PluginLibraryPanelMode.ClientLocal)
            {
                buttonUninstall = topLayout.AddLinkButton(t["uninstall"]);
                buttonUninstall.Enabled = false;
            }

            var basicLayout = groupLayout.AddTableLayout();
            var briefLayout = groupLayout.AddColumnLayout(false, 0, 100, 2);
            var appLayout = groupLayout.AddColumnLayout(true, 2);
            StackLayout nativeLayout = null;
            if (mode == PluginLibraryPanelMode.HostLocal || mode == PluginLibraryPanelMode.HostRemote) nativeLayout = groupLayout.AddColumnLayout(true, 2);

            var row = basicLayout.AddRow();
            row.AddLabel("ID:", TextAlignment.Left);
            var labelID = row.AddLabel("-", true);
            row = basicLayout.AddRow();
            row.AddLabel(t["version"], TextAlignment.Left);
            var labelVersion = row.AddLabel("-", true);

            briefLayout.AddLabel(t["brief"], TextAlignment.Left);
            var textBrief = briefLayout.AddControl(new TextArea{ ReadOnly = true, Text = t["none"] }, true);

            appLayout.AddLabel(t["application-layer"], TextAlignment.Left);
            var textAppLayer = appLayout.AddControl(new TextArea{ ReadOnly = true }, true);

            TextArea textNativeLayer = null;
            if (nativeLayout != null)
            {
                nativeLayout.AddLabel(t["native-layer"], TextAlignment.Left);
                textNativeLayer = nativeLayout.AddControl(new TextArea{ ReadOnly = true }, true);
            }

            if (mode == PluginLibraryPanelMode.HostRemote) initializeRemote(listBox, searchBox);
            else initializeLocal(listBox);

            listBox.SelectedIndexChanged += async delegate
            {
                updatingCheckEnabled = true;

                PackItem item = null;
                PluginPackInfo pack = null;
                if (listBox.SelectedIndex >= 0)
                {
                    item = visibleItems[listBox.SelectedIndex];
                    if (mode == PluginLibraryPanelMode.HostRemote)
                    {
                        pack = await AgencyAsync.GetPluginPackInfo(item.ID);
                        if (pack == null || visibleItems[listBox.SelectedIndex].ID != item.ID)
                        {
                            updatingCheckEnabled = false;
                            return;
                        }
                    }
                    else pack = AgencyLocal.GetPluginPackInfo(item.ID);
                }

                if (pack == null)
                {
                    checkEnabled.Enabled = false;
                    checkEnabled.Text = "-";
                    if (buttonUninstall != null) buttonUninstall.Enabled = false;
                    labelID.Text = labelVersion.Text = "-";
                    textBrief.Text = t["none"];
                    textAppLayer.Text = "";
                    if (textNativeLayer != null) textNativeLayer.Text = "";
                }
                else
                {
                    checkEnabled.Enabled = true;
                    updateCheckEnable(checkEnabled, pack.Status);
                    if (buttonUninstall != null) buttonUninstall.Enabled = true;
                    labelID.Text = item.ID;
                    labelVersion.Text = pack.Version.ToString(3);
                    textBrief.Text = pack.Brief;
                    textAppLayer.Text = pack.AppLayerDetails;
                    if (textNativeLayer != null) textNativeLayer.Text = pack.NativeLayerDetails;
                }
                
                updatingCheckEnabled = false;
            };

            if (buttonUninstall != null) 
            {
                buttonUninstall.Click += async delegate
                {
                    if (listBox.SelectedIndex < 0) return;

                    var item = visibleItems[listBox.SelectedIndex];
                    if (!await AgencyLocal.PopupConfirm(t.Format("confirm-uninstall", item.Name))) return;

                    if (AgencyLocal.UninstallPlugin(item.ID))
                    {
                        visibleItems.Remove(item);
                        allItems.Remove(item);
                        listBox.Items.RemoveAt(listBox.SelectedIndex);
                        Modified = true;
                    }
                };
            }

            buttonInstall.Click += async delegate
            {
                if (mode == PluginLibraryPanelMode.HostRemote)
                {
                    if (caller != null)
                    {
                        await AgencyAsync.RunConsole(caller, "install-plugin.consoleworkflow");
                        Modified = true;
                    }
                }
                else
                {
                    var selected = await App.ShowSelectFolderDialog(this, t["install-title"]);
                    if (selected != null)
                    {
                        if (await AgencyLocal.InstallPlugin(selected))
                        {
                            var itemsToRemove = new List<PackItem>();
                            foreach (var item in allItems)
                            {
                                if (AgencyLocal.GetPluginPackInfo(item.ID) == null) itemsToRemove.Add(item);
                            }
                            foreach (var item in itemsToRemove)
                            {
                                var itemIndex = visibleItems.IndexOf(item);
                                if (itemIndex >= 0) listBox.Items.RemoveAt(itemIndex);
                                visibleItems.Remove(item);
                                allItems.Remove(item);
                            }
                            Modified = true;
                        }
                    }
                }
            };

            checkEnabled.CheckedChanged += async delegate
            {
                if (updatingCheckEnabled) return;
                if (listBox.SelectedIndex < 0) return;

                var item = visibleItems[listBox.SelectedIndex];

                PluginPackInfo pluginInfo = null;
                if (mode == PluginLibraryPanelMode.HostRemote)
                {
                    pluginInfo = await AgencyAsync.GetPluginPackInfo(item.ID);
                    if (pluginInfo == null || visibleItems[listBox.SelectedIndex].ID != item.ID) return;
                }
                else pluginInfo = AgencyLocal.GetPluginPackInfo(item.ID);

                if (checkEnabled.Checked.Value)
                {
                    if (pluginInfo.Status != PluginPackStatus.Disabled) return;
                    if (mode == PluginLibraryPanelMode.HostRemote) await AgencyAsync.EnablePlugin(item.ID);
                    else AgencyLocal.EnablePlugin(item.ID);
                }
                else
                {
                    if (pluginInfo.Status == PluginPackStatus.Disabled) return;
                    if (mode == PluginLibraryPanelMode.HostRemote) await AgencyAsync.DisablePlugin(item.ID);
                    else AgencyLocal.DisablePlugin(item.ID);
                }
                Modified = true;

                if (mode == PluginLibraryPanelMode.HostRemote)
                {
                    pluginInfo = await AgencyAsync.GetPluginPackInfo(item.ID);
                    if (pluginInfo == null || visibleItems[listBox.SelectedIndex].ID != item.ID) return;
                }
                else pluginInfo = AgencyLocal.GetPluginPackInfo(item.ID);

                updateCheckEnable(checkEnabled, pluginInfo.Status);
            };

            searchBox.TextChanged += async delegate
            {
                if (mode == PluginLibraryPanelMode.HostRemote) await updateVisibleItemsRemote(searchBox, listBox);
                else updateVisibleItemsLocal(searchBox.Text, listBox);
                buttonEnableAll.Enabled = buttonDisableAll.Enabled = searchBox.Text.Length == 0;
            };

            buttonDisableAll.Click += async delegate
            {
                if (!await AgencyLocal.PopupConfirm(t["confirm-disable"])) return;
                
                if (mode == PluginLibraryPanelMode.HostRemote)
                {
                    await AgencyAsync.DisableAllPlugins();
                    await updateVisibleItemsRemote(searchBox, listBox);
                }
                else
                {
                    AgencyLocal.DisableAllPlugins();
                    updateVisibleItemsLocal(searchBox.Text, listBox);
                }

                Modified = true;
            };

            buttonEnableAll.Click += async delegate
            {
                if (!await AgencyLocal.PopupConfirm(t["confirm-enable"])) return;

                if (mode == PluginLibraryPanelMode.HostRemote)
                {
                    var tasks = new List<Task>();
                    foreach (var packID in (await AgencyAsync.GetPluginPackIDList()) ?? [])
                    {
                        tasks.Add(AgencyAsync.EnablePlugin(packID));
                    }
                    await Task.WhenAll(tasks);
                    await updateVisibleItemsRemote(searchBox, listBox);

                    Modified = true;
                }
                else
                {
                    foreach (var packID in AgencyLocal.GetPluginPackIDList())
                    {
                        AgencyLocal.EnablePlugin(packID);
                    }
                    updateVisibleItemsLocal(searchBox.Text, listBox);

                    await AgencyLocal.PopupNotice(t["restart-notice"]);
                }
            };
        }

        public bool Modified { get; set; }

        private void updateCheckEnable(CheckBox checkEnable, PluginPackStatus status)
        {
            switch (status)
            {
                case PluginPackStatus.Disabled:
                    checkEnable.Checked = false;
                    checkEnable.Text = t["disabled"];;
                    break;
                case PluginPackStatus.Enabled:
                    checkEnable.Checked = true;
                    checkEnable.Text = t["enabled"];
                    break;
                case PluginPackStatus.ToBeEnabled:
                    checkEnable.Checked = true;
                    checkEnable.Text = t["to-be-enabled"];
                    break;
            }
        }

        private void updateVisibleItemsLocal(String key, ListBox listBox)
        {
            key = key == null ? null : key.ToLower();

            visibleItems.Clear();
            if (String.IsNullOrEmpty(key))
            {
                visibleItems.AddRange(allItems);
            }
            else
            {
                foreach (var item in allItems)
                {
                    if (item.ID.ToLower().Contains(key) || item.Name.ToLower().Contains(key))
                    {
                        visibleItems.Add(item);
                        continue;
                    }
                    var pack = AgencyLocal.GetPluginPackInfo(item.ID);
                    if (pack.Version.ToString().Contains(key) || pack.Brief.ToLower().Contains(key) || pack.AppLayerDetails.ToLower().Contains(key) || pack.NativeLayerDetails.ToLower().Contains(key))
                    {
                        visibleItems.Add(item);
                        continue;
                    }
                }
            }

            listBox.Items.Clear();
            foreach (var item in visibleItems)
            {
                var pack = AgencyLocal.GetPluginPackInfo(item.ID);

                String errorText = "";
                switch (pack.Error)
                {
                    case PluginPackError.Disabled:
                        errorText = t["disabled"];
                        break;
                    case PluginPackError.LoadFailed:
                        errorText = t["load-failed"];
                        break;
                    case PluginPackError.Unlicensed:
                        errorText = t["unlicensed"];
                        break;
                    case PluginPackError.GUIUnsupported:
                        errorText = t["gui-unsupported"];
                        break;
                }
                if (errorText.Length > 0) errorText = " (" + errorText + ")";

                listBox.Items.Add(item.Name + errorText);
            }
        }

        private async Task updateVisibleItemsRemote(SearchBox searchBox, ListBox listBox)
        {
            var key = searchBox.Text ?? "";

            visibleItems.Clear();
            if (String.IsNullOrEmpty(key))
            {
                visibleItems.AddRange(allItems);
            }
            else
            {
                foreach (var item in allItems)
                {
                    if (item.ID.ToLower().Contains(key) || item.Name.ToLower().Contains(key))
                    {
                        visibleItems.Add(item);
                        continue;
                    }
                }
            }

            var targetItems = visibleItems.ToArray();
            var tasks = new Task<PluginPackInfo>[targetItems.Length];
            for (int i = 0; i < targetItems.Length; i++)
            {
                tasks[i] = AgencyAsync.GetPluginPackInfo(targetItems[i].ID);
            }
            var packs = await Task.WhenAll(tasks);
            if (searchBox.Text != key) return;

            listBox.Items.Clear();
            for (int i = 0; i < targetItems.Length; i++)
            {
                var item = targetItems[i];
                var pack = packs[i];

                String errorText = "";
                if (pack == null) errorText = "?";
                else
                {
                    switch (pack.Error)
                    {
                        case PluginPackError.Disabled:
                            errorText = t["disabled"];
                            break;
                        case PluginPackError.LoadFailed:
                            errorText = t["load-failed"];
                            break;
                        case PluginPackError.Unlicensed:
                            errorText = t["unlicensed"];
                            break;
                        case PluginPackError.GUIUnsupported:
                            errorText = t["gui-unsupported"];
                            break;
                    }
                }
                if (errorText.Length > 0) errorText = " (" + errorText + ")";

                listBox.Items.Add(item.Name + errorText);
            }
        }

        private void initializeLocal(ListBox listBox)
        {
            foreach (var id in AgencyLocal.GetPluginPackIDList())
            {
                var pack = AgencyLocal.GetPluginPackInfo(id);
                allItems.Add(new PackItem
                {
                    ID = id,
                    Name = pack.Name,
                });
            }
            allItems.Sort();

            updateVisibleItemsLocal(null, listBox);
        }

        private async void initializeRemote(ListBox listBox, SearchBox searchBox)
        {
            var tasks = new List<Task<PluginPackInfo>>();
            foreach (var id in (await AgencyAsync.GetPluginPackIDList()) ?? [])
            {
                tasks.Add(AgencyAsync.GetPluginPackInfo(id));
            }
            var packs = await Task.WhenAll(tasks);

            foreach (var pack in packs)
            {
                if (pack == null) continue;
                allItems.Add(new PackItem
                {
                    ID = pack.ID,
                    Name = pack.Name,
                });
            }
            allItems.Sort();

            await updateVisibleItemsRemote(searchBox, listBox);
        }

        private class PackItem : IComparable
        {
            public String ID { get; set; }
            public String Name { get; set; }

            public int CompareTo(object obj)
            {
                return Name.CompareTo((obj as PackItem).Name);
            }

            public override string ToString()
            {
                return Name;
            }
        }

        private List<PackItem> allItems = new List<PackItem>();
        private List<PackItem> visibleItems = new List<PackItem>();
        private bool updatingCheckEnabled = false;

        private TextResource t;
    }
}
